#ifdef WIN32
#include "OpenGLExt.h"
#else
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#endif
#include "Texture.h"
#include <cstring>
#include <algorithm>



signed int modifier_table[8][4] =
{
	{-8,  -2,  2,   8 },
	{-17,  -5,  5,  17 },
	{-29,  -9,  9,  29 },
	{-42, -13, 13,  42 },
	{ -60, -18, 18,  60 },
	{-80, -24, 24,  80 },
	{-106, -33, 33, 106 },
	{-183, -47, 47, 183 },
};
uint8_t Read_uint8_t(char **pointer)
{
	uint8_t result;
	memcpy(&result, *pointer, sizeof(uint8_t));
	*pointer += sizeof(uint8_t);
	return result;
}
uint16_t Read_uint16_t(char **pointer)
{
	uint16_t result;
	memcpy(&result, *pointer, sizeof(uint16_t));
	*pointer += sizeof(uint16_t);
	return result;
}
uint32_t Read_uint32_t(char **pointer)
{
	uint32_t result;
	memcpy(&result, *pointer, sizeof(uint32_t));
	*pointer += sizeof(uint32_t);
	return result;
}
unsigned char GetBit(uint64_t value, int index)
{
	return (value >> index) & 0x1;
}
signed char Get3BitSigned(uint64_t value, int index)
{
	if(GetBit(value, index) > 0)
	{
		return ((value >> (index-2)) & 0x7) - 8;
	}
	else
	{
		return (value >> (index-2)) & 0x7;
	}
}
unsigned char Get3Bit(uint64_t value, int index)
{
	return (value >> (index-2)) & 0x7;
}
unsigned char Get4Bit(uint64_t value, int index)
{
	return (value >> (index-3)) & 0xF;
}
unsigned char Get5Bit(uint64_t value, int index)
{
	return (value >> (index-4)) & 0x1F;
}
unsigned char extend_4to8bits(unsigned char value)
{
	return (value << 4) | value;
}
unsigned char extend_5to8bits(unsigned char value)
{
	return (value << 3) | (value >> 2);
}
void Texture::LoadFromFile(FILE* file)
{
	glGenTextures(1,&idGL);
	glBindTexture(GL_TEXTURE_2D,idGL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	//glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	//glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_supported_anisotropy);
	//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_supported_anisotropy);
	//glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,tWidth,tHeight,0,GL_RGB,GL_UNSIGNED_BYTE,textureData);

	long size;
	fseek (file , 0 , SEEK_END);
	size = ftell(file);
	fseek(file , 0 , SEEK_SET);
	char* buffer = new char[size];
	fread(buffer, 1, size, file);
	char* pointer = buffer;

	if(ReadPVRV2Header(&pointer) == true)
	{
#ifdef OS_ANDROID
#else
		format = TEXTURE_FORMAT_R8G8B8;
		DecompressETC(&pointer);
#endif
	}
	else if(ReadTGAHeader(&pointer) == true)
	{
		LoadTGA(&pointer);
	}

	delete[] buffer;
}
bool Texture::ReadPVRV2Header(char** pointer)
{
	if(strncmp(*pointer + 44, "PVR!", 4) != 0)
	{
		return false;
	}
	PVRTexHeaderV2 header;
	header.headerLength = Read_uint32_t(pointer);
	header.height = Read_uint32_t(pointer);
	header.width = Read_uint32_t(pointer);
	header.numMipmaps = Read_uint32_t(pointer);
    header.flags = Read_uint32_t(pointer);
    header.dataLength = Read_uint32_t(pointer);
    header.bpp = Read_uint32_t(pointer);
    header.bitmaskRed = Read_uint32_t(pointer);
    header.bitmaskGreen = Read_uint32_t(pointer);
    header.bitmaskBlue = Read_uint32_t(pointer);
    header.bitmaskAlpha = Read_uint32_t(pointer);
    header.pvrTag = Read_uint32_t(pointer);
    header.numSurfs = Read_uint32_t(pointer);
	//--------------------------------------
	this->width = header.width;
	this->height = header.height;
	this->mipmapCount = header.numMipmaps + 1;
	switch(header.flags & 0xFF)
	{
	case 0x36:
		this->format = TEXTURE_FORMAT_ETC;
		break;
	}
	return true;
}
bool Texture::ReadPVRV3Header(char** pointer)
{
	return false;
}
bool Texture::ReadDDSHeader(char** pointer)
{
	return false;
}
bool Texture::ReadKTXHeader(char** pointer)
{
	return false;
}
bool Texture::ReadTGAHeader(char** pointer)
{
	TGAHeadrer header;
	header.idlength = Read_uint8_t(pointer);
	header.colourmaptype = Read_uint8_t(pointer);
	header.datatypecode = Read_uint8_t(pointer);
	header.colourmaporigin = Read_uint16_t(pointer);
	header.colourmaplength = Read_uint16_t(pointer);
	header.colourmapdepth = Read_uint8_t(pointer);
	header.x_origin = Read_uint16_t(pointer);
	header.y_origin = Read_uint16_t(pointer);
	header.width = Read_uint16_t(pointer);
	header.height = Read_uint16_t(pointer);
	header.bitsperpixel = Read_uint8_t(pointer);
	header.imagedescriptor = Read_uint8_t(pointer);
	if(header.datatypecode == 2)
	{
		if(header.bitsperpixel == 24)
		{
			format = TEXTURE_FORMAT_R8G8B8;
		}
		else if(header.bitsperpixel == 32)
		{
			format = TEXTURE_FORMAT_R8G8B8A8;
		}
	}
	width = header.width;
	height = header.height;
	return true;
}
void Texture::LoadETC(char** pointer)
{
}
void Texture::LoadTGA(char** pointer)
{
	switch(format)
	{
	case TEXTURE_FORMAT_R8G8B8:
		{
			char* buffer = new char[width*height*3];
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					memcpy(buffer + (y*width + x)*3, *pointer + ((height - 1 - y)*width + x)*3 + 2, 1);
					memcpy(buffer + (y*width + x)*3 + 1, *pointer + ((height - 1 - y)*width + x)*3 + 1, 1);
					memcpy(buffer + (y*width + x)*3 + 2, *pointer + ((height - 1 - y)*width + x)*3, 1);
				}
			}
			glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width,height,0,GL_RGB,GL_UNSIGNED_BYTE,buffer);
			delete[] buffer;
			break;
		}
	case TEXTURE_FORMAT_R8G8B8A8:
		char* buffer = new char[width*height*4];
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					memcpy(buffer + (y*width + x)*4, *pointer + ((height - 1 - y)*width + x)*4 + 2, 1);
					memcpy(buffer + (y*width + x)*4 + 1, *pointer + ((height - 1 - y)*width + x)*4 + 1, 1);
					memcpy(buffer + (y*width + x)*4 + 2, *pointer + ((height - 1 - y)*width + x)*4, 1);
					memcpy(buffer + (y*width + x)*4 + 3, *pointer + ((height - 1 - y)*width + x)*4 + 3, 1);
				}
			}
		glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,buffer);
		delete[] buffer;
		break;
	}
}
void Texture::DecompressETC(char** pointer)
{
	int levelWidth = width;
	int levelHeight = height;
	for(int i = 0; i < mipmapCount; i++) //mipmap count
	{
		int workingWidth = max(levelWidth, 4);
		int workingHeight = max(levelHeight, 4);
		char* rawData = new char[workingWidth*workingHeight*3];
		memset(rawData, 0, workingWidth*workingHeight*3);
		for(int row = 0; row < workingHeight/4;row++)
		{
			for(int column = 0;column < workingWidth/4;column++)
			{
				uint64_t block;
				memcpy(&block, *pointer, sizeof(uint64_t));
				*pointer += 8;
				block = _byteswap_uint64(block);
				unsigned char flipbit = GetBit(block, 32);
				unsigned char diffbit = GetBit(block, 33);
				signed int R1, G1, B1, R2, G2, B2;
				unsigned char table_codeword_1, table_codeword_2;
				if (diffbit == 0) //individual mode
				{
					//left block or top block
					R1 =  extend_4to8bits(Get4Bit(block, 63));
					G1 =  extend_4to8bits(Get4Bit(block, 55));
					B1 =  extend_4to8bits(Get4Bit(block, 47));
					//right block or bottom block
					R2 =  extend_4to8bits(Get4Bit(block, 59));
					G2 =  extend_4to8bits(Get4Bit(block, 51));
					B2 =  extend_4to8bits(Get4Bit(block, 43));
				}
				else //differential mode
				{
					//left block or top block
					unsigned char R1a = Get5Bit(block, 63);
					unsigned char G1a = Get5Bit(block, 55);
					unsigned char B1a = Get5Bit(block, 47);

					R1 = extend_5to8bits(R1a);
					G1 = extend_5to8bits(G1a);
					B1 = extend_5to8bits(B1a);
					//right block or bottom block
					signed char dR2 = Get3BitSigned(block, 58);
					signed char dG2 = Get3BitSigned(block, 50);
					signed char dB2 = Get3BitSigned(block, 42);

					R2 = extend_5to8bits(R1a + dR2);
					G2 = extend_5to8bits(G1a + dG2);
					B2 = extend_5to8bits(B1a + dB2);
				}
				table_codeword_1 = Get3Bit(block, 39); //row in table for subblock 1
				table_codeword_2 = Get3Bit(block, 36); //row in table for subblock 1
				if(flipbit == 0) //2x4 side by side
				{
					for(int p = 0; p < 8; p++)
					{
						unsigned char lsb = GetBit(block, p);
						unsigned char msb = GetBit(block, p + 16);
						unsigned char modifyIndex = (((~msb)<<1) & 0x2) | (msb^lsb);
						signed int modifyValue = modifier_table[table_codeword_1][modifyIndex];
						unsigned char R = min(max(R1 + modifyValue, 0), 255);
						unsigned char G = min(max(G1 + modifyValue, 0), 255);
						unsigned char B = min(max(B1 + modifyValue, 0), 255);
						int pixelIndex = row*4*workingWidth + column*4 + (p%4)*workingWidth + p/4;
						rawData[pixelIndex*3] = R;
						rawData[pixelIndex*3 + 1] = G;
						rawData[pixelIndex*3 + 2] = B;
					}
					for(int p = 0; p < 8; p++)
					{
						unsigned char lsb = GetBit(block, p + 8);
						unsigned char msb = GetBit(block, p + 24);
						unsigned char modifyIndex = (((~msb)<<1) & 0x2) | (msb^lsb);
						signed int modifyValue = modifier_table[table_codeword_2][modifyIndex];
						unsigned char R = min(max(R2 + modifyValue, 0), 255);
						unsigned char G = min(max(G2 + modifyValue, 0), 255);
						unsigned char B = min(max(B2 + modifyValue, 0), 255);
						int pixelIndex = row*4*workingWidth + column*4 + (p%4)*workingWidth + p/4 + 2;
						rawData[pixelIndex*3] = R;
						rawData[pixelIndex*3 + 1] = G;
						rawData[pixelIndex*3 + 2] = B;
					}
				}
				else //4x2 top down
				{
					for(int p = 0; p < 8; p++)
					{
						unsigned char lsb = GetBit(block, 2*p - p%2);
						unsigned char msb = GetBit(block, 2*p - p%2 + 16);
						unsigned char modifyIndex = (((~msb)<<1) & 0x2) | (msb^lsb);
						signed int modifyValue = modifier_table[table_codeword_1][modifyIndex];
						unsigned char R = min(max(R1 + modifyValue, 0), 255);
						unsigned char G = min(max(G1 + modifyValue, 0), 255);
						unsigned char B = min(max(B1 + modifyValue, 0), 255);
						int pixelIndex = row*4*workingWidth + column*4 + (p%2)*workingWidth + p/2;
						rawData[pixelIndex*3] = R;
						rawData[pixelIndex*3 + 1] = G;
						rawData[pixelIndex*3 + 2] = B;
					}
					for(int p = 0; p < 8; p++)
					{
						unsigned char lsb = GetBit(block, 2*p - p%2 + 2);
						unsigned char msb = GetBit(block, 2*p - p%2 + 18);
						unsigned char modifyIndex = (((~msb)<<1) & 0x2) | (msb^lsb);
						signed int modifyValue = modifier_table[table_codeword_2][modifyIndex];
						unsigned char R = min(max(R2 + modifyValue, 0), 255);
						unsigned char G = min(max(G2 + modifyValue, 0), 255);
						unsigned char B = min(max(B2 + modifyValue, 0), 255);
						int pixelIndex = (row*4 + 2)*workingWidth + column*4 + (p%2)*workingWidth + p/2;
						rawData[pixelIndex*3] = R;
						rawData[pixelIndex*3 + 1] = G;
						rawData[pixelIndex*3 + 2] = B;
				}
				}
			}
		}
		if(workingWidth > levelWidth || workingHeight > levelHeight)
		{
			char* newData = new char[levelWidth*levelHeight*3];
			for(int y = 0;y<levelHeight;y++)
			{
				memcpy(newData + y*levelWidth*3, rawData + y*workingWidth*3, levelWidth*3);
			}
			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			glTexImage2D(GL_TEXTURE_2D,i,GL_RGB,levelWidth,levelHeight,0,GL_RGB,GL_UNSIGNED_BYTE,newData);
			delete[] newData;
		}
		else
		{
			glTexImage2D(GL_TEXTURE_2D,i,GL_RGB,levelWidth,levelHeight,0,GL_RGB,GL_UNSIGNED_BYTE,rawData);
		}
		delete[] rawData;
		levelWidth = levelWidth >> 1;
		levelHeight = levelHeight >> 1;
	}
}
